
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sys/time.h>
#ifdef __amigaos4__
#include <proto/exec.h>
#else
#define TRUE 1
#define FALSE 0
#endif

#ifdef USE_ALTIVEC
#include <altivec.h>
#endif

#define NB_SECONDS 12

#define BUFFER_SIZE_BYTES (1024 * 768 * 4)
#define BUFFER_SIZE_WORDS (BUFFER_SIZE_BYTES / sizeof(int))
#define BUFFER_SIZE_FLOATS (BUFFER_SIZE_BYTES / sizeof(float))
#define BUFFER_SIZE_DOUBLES (BUFFER_SIZE_BYTES / sizeof(double))
#define BUFFER_SIZE_MEGABYTES (BUFFER_SIZE_BYTES / (1024 * 1024))

static void buffer_fill(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      buffer[i] = value;
   }
}

// Check that a buffer is filled with a given value.
// Return TRUE if success
static int buffer_check_fill_value(int *buffer, int len, int value)
{
   int i;

   for (i=0 ; i<len ; i++) {
      if (buffer[i] != value) {
         printf("buffer_check_fill_value failed at offset %d (expected 0x%08x, found 0x%08x)\n", i, value, buffer[i]);
         return FALSE;
      }
   }

   return TRUE;
}

int main(int argc, char **argv)
{
   int *src_buf, *src_buf_addr;
   int *dest_buf, *dest_buf_addr;
   int i, n, nloops;
   struct timeval tv_start, tv_stop, tv_diff;
   float ftime;
   volatile char *src_cptr, *dest_cptr;
   volatile int *src_iptr, *dest_iptr;
   volatile float *src_fptr, *dest_fptr;
   volatile double *src_dptr, *dest_dptr;
#ifdef USE_ALTIVEC
   vector unsigned int *vptr;
   vector unsigned int value = vec_splat_u32(0);
   vector unsigned int value2 = vec_splat_u32(0);
#endif

   printf("sizeof(float) = %d\n", sizeof(float));
   printf("sizeof(double) = %d\n", sizeof(double));
   printf("sizeof(wchar_t) = %d\n", sizeof(wchar_t));

   /*
    * Allocate and prepare buffers
    */
   src_buf_addr = malloc(BUFFER_SIZE_BYTES + 128);
   if (!src_buf_addr) {
      printf("Impossible to allocate source buffer\n");
      return 1;
   }
   printf("Allocated %.1f KB buffer at %p\n", (float)BUFFER_SIZE_BYTES / 1024, src_buf_addr);
   src_buf = src_buf_addr;
   if ((int)src_buf_addr & 0x1f) {
      src_buf = (int *)(((int)src_buf_addr + 32) & ~0x1f);
      printf("Buffer not aligned, realign it at %p\n", src_buf);
   }

   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   if (buffer_check_fill_value(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == FALSE) {
      printf("Check buffer is filled of 0xdeadcafe : failure\n");
   }
   dest_buf_addr = malloc(BUFFER_SIZE_BYTES + 128);
   if (!dest_buf_addr) {
      printf("Impossible to allocate destination buffer\n");
      return 2;
   }
   printf("Allocated %.1f KB buffer at %p\n", (float)BUFFER_SIZE_BYTES / 1024, dest_buf_addr);
   dest_buf = dest_buf_addr;
   if ((int)dest_buf_addr & 0x1f) {
      dest_buf = (int *)(((int)dest_buf_addr + 32) & ~0x1f);
      printf("Buffer not aligned, realign it at %p\n", dest_buf);
   }

   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == FALSE) {
      printf("Check buffer is filled of 0xdeadcafe : failure\n");
   }

   /*
    * Really allocate the pages
    * Linux only allocates pages the first time they are accessed, so run a first loop
    * for that and to compute the number of loops in the following tests, based on the
    * slowest method.
    */
   gettimeofday(&tv_start, NULL);
   nloops = 0;
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0);

   do {
      src_cptr = (char *)src_buf;
      dest_cptr = (char *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_BYTES ; i++) {
         *dest_cptr++ = *src_cptr++;
      }
      gettimeofday(&tv_stop, NULL);
      timersub(&tv_stop, &tv_start, &tv_diff);
      nloops++;
   } while (tv_diff.tv_sec < NB_SECONDS);
   printf("Calibration done: %d.%03d s, each test will run %d loops\n", (int)tv_diff.tv_sec, (int)(tv_diff.tv_usec / 1000), nloops);
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by memcpy
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      memcpy(dest_buf, src_buf, BUFFER_SIZE_BYTES);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by memcpy : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by char
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      src_cptr = (char *)src_buf;
      dest_cptr = (char *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_BYTES ; i++) {
         *dest_cptr++ = *src_cptr++;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by char : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by int
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      src_iptr = (int *)src_buf;
      dest_iptr = (int *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_WORDS ; i++) {
         *dest_iptr++ = *src_iptr++;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by int : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by float
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      src_fptr = (float *)src_buf;
      dest_fptr = (float *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_FLOATS ; i++) {
         *dest_fptr++ = *src_fptr++;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by float : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by double
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      src_dptr = (double *)src_buf;
      dest_dptr = (double *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_DOUBLES ; i++) {
         *dest_dptr++ = *src_dptr++;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by double : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by int with loop unrolled
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      src_iptr = (int *)src_buf;
      dest_iptr = (int *)dest_buf;
      for (i=0 ; i< BUFFER_SIZE_WORDS / 8; i++) {
         dest_iptr[0] = src_iptr[0];
         dest_iptr[1] = src_iptr[1];
         dest_iptr[2] = src_iptr[2];
         dest_iptr[3] = src_iptr[3];
         dest_iptr[4] = src_iptr[4];
         dest_iptr[5] = src_iptr[5];
         dest_iptr[6] = src_iptr[6];
         dest_iptr[7] = src_iptr[7];
         dest_iptr += 8;
         src_iptr += 8;
      }
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by int with loop unrolled : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

#ifdef __amigaos4__
   /*
    * Copy by CopyMem
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      CopyMem(src_buf, dest_buf, BUFFER_SIZE_BYTES);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by CopyMem : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }

   /*
    * Copy by CopyMemQuick
    */
   buffer_fill(src_buf, BUFFER_SIZE_WORDS, 0xdeadcafe);
   buffer_fill(dest_buf, BUFFER_SIZE_WORDS, 0);
   gettimeofday(&tv_start, NULL);
   for (n=0 ; n<nloops ; n++) {
      CopyMemQuick(src_buf, dest_buf, BUFFER_SIZE_BYTES);
   }
   gettimeofday(&tv_stop, NULL);
   timersub(&tv_stop, &tv_start, &tv_diff);
   ftime = tv_diff.tv_sec + ((float)tv_diff.tv_usec / 1000000);
   printf("Copy by CopyMemQuick : time = %.03f s, bandwidth = %d MB/s\n", ftime, (int)(BUFFER_SIZE_MEGABYTES * nloops / ftime));
   if (buffer_check_fill_value(dest_buf, BUFFER_SIZE_WORDS, 0xdeadcafe) == 0) {
      printf("Check buffer is filled of 0 : failure\n");
   }
#endif

   /*
    * Free resources and exit
    */
   free(src_buf_addr);
   free(dest_buf_addr);

   return 0;
}
